<!DOCTYPE html>
<html>
<head>
  <style>
    html, body {
      margin: 0;
      overflow: hidden;
      height: 100%;
    }
  </style>
  <title>WebGL training - Model Matrix</title>
</head>
<body>
  <canvas id="canvas"></canvas>
  <script type="module">
    import {
      GUI,
      vec3,
      quat,
      mat4,
      toHighDPI,
      createProgram,
      createVertexArray,
      createTexture,
    } from '../js/bundle.js';

    let ready = false;
    const config = {
      clearColor: [0, 0, 0],
    };
    const meshes = [
      {
        geometry: {
          data: new Float32Array([
            -1, -1, 1, 0, 0, 0, 0,
            1, -1, 1, 1, 1, 1, 0,
            -1, 1, 1, 1, 1, 0, 1,
            1, 1, 0, 0, 1, 1, 1,
            1, -1, 1, 1, 1, 1, 0,
            -1, 1, 1, 1, 1, 0, 1,
          ]),
          stride: 7 * 4,
          attributes: [
            {
              index: 0,
              size: 2,
              offset: 0,
            },
            {
              index: 1,
              size: 3,
              offset: 2 * 4,
            },
            {
              index: 2,
              size: 2,
              offset: 5 * 4,
            },
          ],
        },
        material: {
          color: [255, 255, 255],
          image: '../asset/images/crate.gif',
        },
        tx: 0.0,
        ty: 0.0,
        tz: 0.0,
        sx: 1.0,
        sy: 1.0,
        sz: 1.0,
        rx: 0.0,
        ry: 0.0,
        rz: 0.0,
        translate: vec3.create(),
        scale: vec3.create(),
        quat: quat.create(),
        matrix: mat4.create(),
      },
    ];
    const canvas = document.getElementById('canvas');
    // 01: create WebGL context
    const gl = canvas.getContext('webgl2');
    // 02.1: create program from shader source
    const program = createProgram(gl, `#version 300 es
      precision highp float;

      layout(location = 0) in vec2 a_position;
      layout(location = 1) in vec3 a_color;
      layout(location = 2) in vec2 a_uv;
      uniform mat4 u_modelMatrix;
      out vec3 v_color;
      out vec2 v_uv;

      void main () {
        vec4 position = vec4(a_position, 0.0, 1.0);
        position = u_modelMatrix * position;
        gl_Position = position;
        v_color = a_color;
        v_uv = vec2(a_uv.x, 1.0 - a_uv.y);
      }
    `, `#version 300 es
      precision highp float;

      uniform vec3 u_color;
      uniform sampler2D u_texture;
      in vec3 v_color;
      in vec2 v_uv;
      out vec4 fragColor;

      void main () {
        vec4 color = texture(u_texture, v_uv);
        fragColor = vec4(color.rgb * v_color * u_color, 1.0);
      }
    `);
    // 02.2: get uniform locations
    const modelMatrixUniform = gl.getUniformLocation(program, 'u_modelMatrix');
    const colorUniform = gl.getUniformLocation(program, 'u_color');
    const textureUniform = gl.getUniformLocation(program, 'u_texture');

    meshes.forEach((mesh) => {
      // 03: create vao
      mesh.vao = createVertexArray(gl, mesh.geometry);
    });

    function render() {
      if (!ready) {
        return;
      }
      // 05: clean canvas before drawing
      gl.clearColor(
        config.clearColor[0] / 255,
        config.clearColor[1] / 255,
        config.clearColor[2] / 255,
        1,
      );
      gl.clear(gl.COLOR_BUFFER_BIT);

      // 06: use program
      gl.useProgram(program);

      meshes.forEach((mesh) => {
        // 07.1: set color uniform
        gl.uniform3f(
          colorUniform,
          mesh.material.color[0] / 255,
          mesh.material.color[1] / 255,
          mesh.material.color[2] / 255,
        );
        // 07.2: set texture uniform
        gl.uniform1i(textureUniform, 0);
        // 07.3: set model matrix uniform
        gl.uniformMatrix4fv(modelMatrixUniform, false, mesh.matrix);

        // 08: bind texture
        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, mesh.material.texture);

        // 09: bind vao
        gl.bindVertexArray(mesh.vao);

        // 10: draw triangles
        gl.drawArrays(gl.TRIANGLES, 0, mesh.geometry.count);
      });

      gl.useProgram(null);
    }

    // handle resize
    function resize() {
      const { clientWidth, clientHeight } = document.documentElement;
      canvas.width = clientWidth;
      canvas.height = clientHeight;
      // handle high DPI screen
      toHighDPI(canvas);
      // set viewport
      gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
      render();
    }

    window.onresize = resize;
    resize();

    // 04: create texture
    (async () => {
      const textures = await Promise.all(meshes.map(mesh => createTexture(gl, mesh.material.image)));
      textures.forEach((texture, i) => {
        meshes[i].material.texture = texture;
      });
      ready = true;
      render();
    })();

    // config
    const gui = new GUI();
    gui.addColor(config, 'clearColor').onChange(() => {
      render();
    });
    const mesh = meshes[0];
    gui.addColor(mesh.material, 'color').onChange(() => {
      render();
    });
    const setMatrix = () => {
      vec3.set(mesh.translate, mesh.tx, mesh.ty, mesh.tz);
      vec3.set(mesh.scale, mesh.sx, mesh.sy, mesh.sz);
      quat.fromEuler(mesh.quat, mesh.rx, mesh.ry, mesh.rz);
      mat4.fromRotationTranslationScale(mesh.matrix, mesh.quat, mesh.translate, mesh.scale);
      render();
    };
    const modelFolder = gui.addFolder('Model Matrix');
    modelFolder.add(mesh, 'tx', -2, 2, 0.1).onChange(() => {
      setMatrix();
    });
    modelFolder.add(mesh, 'ty', -2, 2, 0.1).onChange(() => {
      setMatrix();
    });
    modelFolder.add(mesh, 'tz', -2, 2, 0.1).onChange(() => {
      setMatrix();
    });
    modelFolder.add(mesh, 'sx', 0, 2, 0.1).onChange(() => {
      setMatrix();
    });
    modelFolder.add(mesh, 'sy', 0, 2, 0.1).onChange(() => {
      setMatrix();
    });
    modelFolder.add(mesh, 'sz', 0, 2, 0.1).onChange(() => {
      setMatrix();
    });
    modelFolder.add(mesh, 'rx', -360, 360, 10).onChange(() => {
      setMatrix();
    });
    modelFolder.add(mesh, 'ry', -360, 360, 10).onChange(() => {
      setMatrix();
    });
    modelFolder.add(mesh, 'rz', -360, 360, 10).onChange(() => {
      setMatrix();
    });
  </script>
</body>
</html>